package com.github.common.extend.demo.controller;

import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Stream;

/**
 * 直接讲这个类copy到支持spring web环境的项目里即可使用，或者利用其他web处理器接入
 * （执行权限很大，结合实际情况判断是否开启）
 * @author jie
 * @date 2021/12/1
 * @description
 */
@ConditionalOnProperty(value = "dynamic-method-invoker.enable" ,havingValue = "true")
@RestController
public class DynamicMethodInvokerController implements ApplicationContextAware, BeanClassLoaderAware {

    private ApplicationContext applicationContext;

    private ClassLoader classLoader;

    /**
     * 利用反射动态执行方法
     * @param methodParams
    {
        "beanName":"beanName",
        "className":null,
        "methodSignature":"method0(java.util.List<java.lang.String> ,java.lang.String)"
        "args":[
            "{\"pro0\":\"1\",\"pro1\":\"2\"}",
            "12"
        ]
    }
     * @return
     */
    @PostMapping("/dynamic/invoke/method")
    public Object dynamicInvokeMethod(@RequestBody MethodParams methodParams)
    {
        return delegateInvoke(methodParams);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 执行指定方法
     * @param methodParams
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    /**
     * 执行指定方法
     * @param methodParams
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws ClassNotFoundException
     */
    public Object delegateInvoke(MethodParams methodParams)
    {
        System.out.println(MessageFormat.format("requests params {0}" ,methodParams));
        //获取执行对象
        String beanName = methodParams.getBeanName();
        Object invoker = null;
        Class invokerClass = null;
        if (!StringUtils.isEmpty(beanName)){
            invoker = applicationContext.getBean(beanName);
            invokerClass = invoker.getClass();
        }else {
            String className = methodParams.getClassName();
            if (StringUtils.isEmpty(className)){
                throw new IllegalArgumentException("className is null");
            }
            try {
                invokerClass = classLoader.loadClass(className);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e);
            }
        }

        Method invokeMethod = matchMethod(invokerClass, methodParams);
        Type[] genericParameterTypes = methodParams.getGenericParameterTypes();
        Object[] args = methodParams.getArgs();
        if (args == null || args.length != genericParameterTypes.length){
            throw new IllegalArgumentException("方法参数值不匹配");
        }

        for (int i = 0; i < genericParameterTypes.length; i++) {
            Object value = args[i];
            if (!(value instanceof String)){
                value = JSON.toJSONString(value);
            }
            args[i] = String.class == genericParameterTypes[i] ?
                    value : JSON.parseObject((String) value, genericParameterTypes[i]);
        }

        boolean accessible = invokeMethod.isAccessible();
        try {
            invokeMethod.setAccessible(true);
            if (Modifier.isStatic(invokeMethod.getModifiers())) {
                try {
                    return invokeMethod.invoke(invokerClass ,args);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new IllegalStateException(e);
                }
            }else {
                if (invoker == null){
                    Map beansOfType = applicationContext.getBeansOfType(invokerClass);
                    if (beansOfType.size() > 1){
                        try {
                            invoker = applicationContext.getBean(invokerClass);
                        }catch (Exception e){
                            throw new IllegalArgumentException(MessageFormat.format("found {0} match bean," +
                                    " please specify bean name" ,beansOfType.size()));
                        }
                    }else if (beansOfType.size() == 1){
                        invoker = beansOfType.values().stream().findFirst().orElse(null);
                    }else {
                        //尝试通过无参构造函数创建
                        try {
                            invoker = invokerClass.newInstance();
                        } catch (InstantiationException | IllegalAccessException e) {
                            throw new IllegalStateException(e);
                        }
                    }
                }
                try {
                    return invokeMethod.invoke(invoker ,args);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new IllegalStateException(e);
                }
            }
        }finally {
            invokeMethod.setAccessible(accessible);
        }
    }

    /**
     * 根据{@link Type#getTypeName()}生成Type
     * @param typeName
     * @param classLoader
     * @return
     */
    public static Type parseType(String typeName ,ClassLoader classLoader)
    {
        if (typeName == null || typeName.length() == 0){
            throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName));
        }
        typeName.trim();
        int length = typeName.length();
        char lastChar = typeName.charAt(length - 1);
        if (lastChar == ']'){
            if (length <= 2){
                throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName));
            }
            if (typeName.charAt(length - 2) == '['){
                return GenericArrayTypeImpl.make(parseType(typeName.substring(0 ,length - 2) ,classLoader));
            }else {
                throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName));
            }
        }else if (lastChar == '>'){
            if (length <= 2){
                throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName));
            }
            try {
                int startSubTypeIndex = typeName.indexOf("<");
                Class rawClass = classLoader.loadClass(typeName.substring(0 ,startSubTypeIndex));

                String actualTypeArgumentsName = typeName.substring(startSubTypeIndex + 1 ,length - 1);

                return ParameterizedTypeImpl.make(rawClass ,parseTypes(actualTypeArgumentsName ,classLoader) ,null);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName) ,e);
            }
        }else {
            try {
                return classLoader.loadClass(typeName);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName) ,e);
            }
        }
    }

    /**
     * 根据{@link Type#getTypeName()}生成Type
     * @param typeNames 多个{@link Type#getTypeName()}之间通过逗号分割
     *                  例：java.util.List<java.lang.String>, java.lang.String
     * @param classLoader
     * @return
     */
    public static Type[] parseTypes(String typeNames ,ClassLoader classLoader){
        if (typeNames == null || typeNames.length() == 0){
            return new Type[0];
        }

        List<String> actualTypeArgumentNames = new ArrayList<>();
        StringBuilder currentActualTypeArgumentName = new StringBuilder();
        int leftCount = 0;
        for (int i = 0; i < typeNames.length(); i++) {
            char c = typeNames.charAt(i);
            if (c == ' '){
                continue;
            }
            if (c == ',' && leftCount == 0){
                actualTypeArgumentNames.add(currentActualTypeArgumentName.toString());
                currentActualTypeArgumentName = new StringBuilder();
                continue;
            }

            if (c == '<'){
                leftCount++;
            } else if (c == '>'){
                leftCount--;
            }
            currentActualTypeArgumentName.append(c);
        }

        if (currentActualTypeArgumentName.length() > 0){
            actualTypeArgumentNames.add(currentActualTypeArgumentName.toString());
        }

        Type[] actualTypeArguments = new Type[actualTypeArgumentNames.size()];
        for (int i = 0; i < actualTypeArgumentNames.size(); i++) {
            actualTypeArguments[i] = parseType(actualTypeArgumentNames.get(i) ,classLoader);
        }

        return actualTypeArguments;
    }

    /**
     * 根据{@link Type#getTypeName()}获取{@link Class}
     * @param typeName
     * @param classLoader
     * @return
     */
    public static Class parseTypeName2Class(String typeName , ClassLoader classLoader){
        if (typeName == null || typeName.length() == 0){
            throw new IllegalArgumentException();
        }

        Class<?> basicType = tryParseBasicType(typeName);
        if (basicType != null){
            return basicType;
        }

        int arrayDimension = 0;
        int i = typeName.length() - 1;
        while (true) {
            if (typeName.charAt(i) == ']') {
                if (typeName.charAt(--i) != '['){
                    throw new IllegalArgumentException(MessageFormat.format("非法的typeName,{0}" ,typeName));
                }
                i--;
                arrayDimension++;
            }else {
                break;
            }
        }
        String rawTypeName = typeName.substring(0 ,i + 1);
        int index = rawTypeName.indexOf("<");
        if (index >= 0){
            rawTypeName = rawTypeName.substring(0 ,index);
        }
        StringBuilder className = new StringBuilder();
        for (int i1 = 0; i1 < arrayDimension; i1++) {
            className.append('[');
        }
        if (arrayDimension > 0) {
            //数组
            if (int.class.getName().equals(rawTypeName)) {
                className.append('I');
            } else if (boolean.class.getName().equals(rawTypeName)) {
                className.append('Z');
            } else if (byte.class.getName().equals(rawTypeName)) {
                className.append('B');
            } else if (char.class.getName().equals(rawTypeName)) {
                className.append('C');
            } else if (short.class.getName().equals(rawTypeName)) {
                className.append('S');
            } else if (float.class.getName().equals(rawTypeName)) {
                className.append('F');
            } else if (double.class.getName().equals(rawTypeName)) {
                className.append('D');
            } else if (long.class.getName().equals(rawTypeName)) {
                className.append('J');
            }else {
                className.append('L').append(rawTypeName).append(';');
            }
        }else {
            className.append(rawTypeName);
        }

        try {
            return Class.forName(className.toString() ,false ,classLoader);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 尝试解析基本类型
     * @param typeName
     * @return
     */
    public static Class<?> tryParseBasicType(String typeName) {
        switch (typeName) {
            case "int":     return int.class;
            case "long":    return long.class;
            case "boolean": return boolean.class;
            case "double":  return double.class;
            case "float":   return float.class;
            case "char":    return char.class;
            case "byte":    return byte.class;
            case "short":   return short.class;
            case "void":    return void.class;
            default:        return null;
        }
    }

    /**
     * 根据{@link Type#getTypeName()}获取{@link Class}
     * @param typeName
     * @return
     */
    public static Class parseTypeName2Class(String typeName){
        return parseTypeName2Class(typeName ,ClassLoader.getSystemClassLoader());
    }

    private Method matchMethod(Class<?> invokerClass ,MethodParams methodParams)
    {
        String methodName = methodParams.getMethodName();

        Class[] parameterClasses = Stream.of(methodParams.getGenericParameterTypes())
                .map(type -> parseTypeName2Class(type.getTypeName()))
                .toArray(i -> new Class[i]);

        List<Class> classes = new LinkedList<>();
        classes.add(invokerClass);
        while (!classes.isEmpty())
        {
            Class clazz = classes.remove(0);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if (!method.getName().equals(methodName)){
                    continue;
                }

                int parameterCount = method.getParameterCount();
                if (parameterCount != parameterClasses.length) {
                    continue;
                }

                Class<?>[] parameterTypes = method.getParameterTypes();

                boolean match = true;
                for (int i = 0; i < parameterCount; i++) {
                    if (!parameterTypes[i].isAssignableFrom(parameterClasses[i])){
                        match = false;
                        break;
                    }
                }

               if (match) {
                    return method;
                }
            }
            Class superclass = clazz.getSuperclass();
            if (superclass != null) {
                classes.add(0, superclass);
            }
            for (Class anInterface : clazz.getInterfaces()) {
                classes.add(anInterface);
            }
        }

        throw new IllegalArgumentException(MessageFormat.format("not found method {0}" ,methodParams));
    }

    @Data
    @Accessors(chain = true)
    public static class MethodParams  {

        /**
         * 优先级大于{@link #className}
         */
        private String beanName;

        private String className;

        /**
         * 格式: methodName(GenericParameterType type0 ,GenericParameterType type1 ,...)
         * 例: method0(List<java.lang.String> ,java.lang.String)
         *
         */
        private String methodSignature;

        /**
         * 按参数顺序
         */
        private Object[] args;

        public String getMethodName()
        {
            String methodSignature = getMethodSignature();
            return methodSignature.substring(0, methodSignature.indexOf("("));
        }

        public Type[] getGenericParameterTypes(){
            String genericParameterTypeNames = methodSignature.substring(
                    methodSignature.indexOf("(") + 1, methodSignature.length() - 1);
            return parseTypes(genericParameterTypeNames, ClassLoader.getSystemClassLoader());
        }

    }

}
